#include "process.h"
#include "memory.h"
#include "string.h"
#include "tss.h"
#include "console.h"
#include "bitmap.h"
#include "interrupt.h"
#include "list.h"
#include "debug.h"

extern void intr_exit(void);
extern struct list ready_list;
extern struct list all_list;

void start_process(void* filename_){
    void* function = filename_;
    struct task_struct* cur = running_task();
    /* 越过线程栈 指向intr_stack的结构体 */
    cur->self_kstack += sizeof(struct thread_stack);
    /* 为intr_stack结构体填充初始化环境 */
    struct intr_stack* proc_stack = (struct intr_stack*)cur->self_kstack;
    proc_stack->eax = proc_stack->ebx = proc_stack->ecx = proc_stack->edx = 0;

    proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;

    proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;
    proc_stack->cs = SELECTOR_U_CODE;
    proc_stack->ss = SELECTOR_U_DATA;

    /* 不允许用户进程操作显存 */
    proc_stack->gs = 0;

    proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);
    proc_stack->eip = function;

    asm volatile("mov %0, %%esp; jmp intr_exit" : : "g" (proc_stack) : "memory");
}
/* 激活页表 */
void page_dir_activate(struct task_struct* p_thread) {
    ASSERT(p_thread != NULL);
    /* 内核的页目录表地址是0x100000 即1M之后的4K大小
     * 由于我们不知道是否线程切换进程还是进程切换线程
     * 所以只要当前线程不是进程 我们都更新一下cr3寄存器 即页表地址寄存器
     * 如果是进程 获取进程页表的物理地址后存入cr3
     */
    uint32_t pagedir_phy_addr = 0x100000;

    if(p_thread->pgdir != NULL){
        pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);
    }

    asm volatile("movl %0, %%cr3" : : "r" (pagedir_phy_addr) : "memory");
}

void process_activate(struct task_struct* p_thread){
    ASSERT(p_thread != NULL);
    page_dir_activate(p_thread);

    /* 内核线程特权级本身就是0,处理器进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0 */
    if(p_thread->pgdir != NULL){
        /* 更新该进程的esp0,用于此进程被中断时保留上下文 */
        update_tss_esp(p_thread);
    }
}

/* 创建页目录表,将当前页表的表示内核空间的pde复制,
 * 成功则返回页目录的虚拟地址,否则返回-1 */
uint32_t* create_page_dir(void) {
    /* 为页目录表获取内存空间 */
    uint32_t* page_dir_vaddr = get_kernel_pages(1);
    if(page_dir_vaddr == NULL){
        console_put_str("create_page_dir: get_kernel_page failed!");
        return NULL;
    }
    /* 将768之后的pde更新为内核页目录表的pde
     * 即0xc0000000之后的地址空间为内核的
     * 可以实现进程共享内核
     */
    memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 768 * 4), (uint32_t*)(0xfffff000 + 768 * 4), 256 * 4);

    /* 获取页目录表的物理地址 */
    uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)page_dir_vaddr);
    /* 将页目录表最后一项指向本身，用来modify pde & pte */
    page_dir_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;

    return page_dir_vaddr;
}

void create_user_vaddr_bitmap(struct task_struct* user_prog){
    user_prog->userprog_vaddr.vbtmp.btmp_bytes_len = \
                            (0xc0000000 - USER_VADDR_START) / 8 / PG_SIZE;
    /* 向上取整获取bitmap需要的页数 */
    uint32_t bitmap_pg_cnt = DIV_ROUND_UP(user_prog->userprog_vaddr.vbtmp.btmp_bytes_len,\
                                          PG_SIZE);

    user_prog->userprog_vaddr.vbtmp.bits = get_kernel_pages(bitmap_pg_cnt);
    user_prog->userprog_vaddr.vaddr_start = USER_STACK3_VADDR;
    bitmap_init(&user_prog->userprog_vaddr.vbtmp);
}

void process_execute(void* filename, char* name) {
    struct task_struct* thread = get_kernel_pages(1);

    init_thread(thread, name, DEFAULT_PRIO);
    create_user_vaddr_bitmap(thread);
    thread_create(thread, start_process, filename);
    thread->pgdir = create_page_dir();
    
    enum intr_status old_status = intr_disable();

    ASSERT(!elem_find(&ready_list, &thread->general_tag));
    list_append(&ready_list, &thread->general_tag);

    ASSERT(!elem_find(&all_list, &thread->all_tag));
    list_append(&all_list, &thread->all_tag);

    intr_set_status(old_status);
}